During the execution of Java applications, it is often necessary for a thread to obtain a lock (i.e. synchronize) on an object. By obtaining the lock, the thread ensures that, while it is operating on the object or a resource associated with the object, the object or the associated resource will not be modified by another thread. This helps to ensure data consistency and integrity.
Traditionally, a thread obtains a lock on an object by invoking a locking function of the Java virtual machine (JVM). In response to this invocation, the locking function (which is now being executed by the thread) creates a heavy-weight lock (HL) data structure, and associates the HL data structure with the object that is being locked. In addition, the locking function calls down to the operating system (OS) and requests an OS level locking structure (e.g. a mutex). After the mutex is obtained and associated with the HL data structure, the locking function calls down to the OS again to lock the mutex. Once that is done, the thread owns a lock on the mutex and no other thread will be allowed to lock the object until this thread releases the lock.
According to the above locking methodology, an HL data structure is created and a mutex is requested when an object is locked. The creation of a HL data structure and the setup of the mutex is relatively resource intensive. It has been observed that, in a majority of cases in which a lock is obtained on an object, no contention actually occurs. That is, a thread obtains the lock and releases the lock on the object before any other thread tries to obtain a lock on that object. Thus, in most cases, the HL data structure and the mutex are not used, and the locking overhead is incurred needlessly. In light of this observation, some JVM's have been enhanced to implement a fast locking methodology. According to this approach, a JVM does not necessarily create an HL data structure when an object is locked. Rather, the JVM creates a light-weight, fast lock (FL) data structure, which is much less resource intensive to create than the HL data structure. Only when there is actual contention will the JVM create the HL data structure and request a mutex. By doing so, the JVM reduces the amount of overhead that is incurred as a result of locking.
One approach that has been used to implement fast locking is as follows. When a first thread desires a lock on an object, it invokes the locking function of the JVM. In response to this invocation, the locking function (which is now being executed by the first thread), sees that this is the first request to lock the object; hence, it creates an FL data structure and associates it with the object. The locking function does not create an HL data structure, nor does it call down to the OS to obtain a mutex. If the first thread releases the lock on the object before any other thread tries to lock that same object, then the locking function simply frees the FL data structure (to be used by other threads), and the HL data structure is never created.
If, however, a second thread requests a lock on the object while the first thread has a lock on the object, then the locking function will create the HL data structure. Specifically, when the second thread invokes the locking function of the JVM, the locking function (which is now being executed by the second thread) sees the FL data structure that has already been created, and knows that the first thread has already locked the object. Thus, it knows that there is lock contention. In response, the locking function creates an HL data structure, and associates it with the object. Thereafter, the locking function calls down to the OS and requests a mutex. After the mutex is obtained and associated with the HL data structure, the locking function calls down to the OS again to lock the mutex. As part of this process, the locking function causes ownership of the lock on the mutex to be assigned to the first thread (note: even though it is the second thread that is requesting the lock on the mutex, the ownership of the lock is assigned to the first thread because that is the thread that should currently own the lock). Some operating systems allow this to be done. Once that is done, the first thread owns an actual lock on the object. Thereafter, the locking function calls down to the OS again and tries to lock on the mutex, this time on behalf of the second thread. Because the mutex is now locked by the first thread, the second thread cannot lock on the mutex. As a result, the OS puts the second thread to sleep (i.e. the second thread blocks and waits). The second thread will be awakened by the OS at some point when the lock on the mutex has been released. At that point, the second thread will be allowed to contend for and possibly obtain a lock on the mutex. In this manner, the JVM implements fast locking, and creates an HL data structure and requests a mutex only when there is actual lock contention.
In the above approach, the JVM relies upon the ability of an operating system to allow one thread to cause the lock on a mutex to be assigned to another thread. Unfortunately, many operating systems do not have this capability. For those operating systems, the above fast locking approach cannot be implemented.